const ASRClient = require('../asrClient');
const WebSocket = require('ws');

// Mock WebSocket
jest.mock('ws');

describe('ASRClient 状态机测试', () => {
  let client;
  let mockWs;

  beforeEach(() => {
    // 重置所有mock
    jest.clearAllMocks();
    
    // 创建WebSocket mock
    mockWs = {
      on: jest.fn(),
      send: jest.fn(),
      close: jest.fn(),
      readyState: WebSocket.OPEN
    };
    
    WebSocket.mockImplementation(() => mockWs);
    
    // 创建客户端实例
    client = new ASRClient('ws://test:8888');
    
    // 重置状态到初始状态，因为构造函数会调用connect()
    client.currentState = 'disconnected';
    client.stateHistory = [];
  });

  afterEach(() => {
    if (client) {
      client = null;
    }
  });

  describe('初始化和基本状态', () => {
    test('应该以DISCONNECTED状态初始化', () => {
      expect(client.getCurrentState()).toBe('disconnected');
    });

    test('应该正确设置初始属性', () => {
      expect(client.url).toBe('ws://test:8888');
      expect(client.reconnectAttempts).toBe(0);
      expect(client.maxReconnectAttempts).toBe(5);
      expect(client.reconnectDelay).toBe(1000);
      expect(client.getStateHistory()).toEqual([]);
    });
  });

  describe('状态转换验证', () => {
    test('应该验证有效的状态转换', () => {
      expect(client.isValidStateTransition('disconnected', 'connecting')).toBe(true);
      expect(client.isValidStateTransition('connecting', 'connected')).toBe(true);
      expect(client.isValidStateTransition('connected', 'idle')).toBe(true);
      expect(client.isValidStateTransition('idle', 'audio_streaming')).toBe(true);
      expect(client.isValidStateTransition('audio_streaming', 'processing_audio')).toBe(true);
      expect(client.isValidStateTransition('processing_audio', 'idle')).toBe(true);
    });

    test('应该拒绝无效的状态转换', () => {
      expect(client.isValidStateTransition('disconnected', 'idle')).toBe(false);
      expect(client.isValidStateTransition('idle', 'connected')).toBe(false);
      expect(client.isValidStateTransition('processing_audio', 'audio_streaming')).toBe(false);
      expect(client.isValidStateTransition('file_uploading', 'audio_streaming')).toBe(false);
    });

    test('应该记录状态转换历史', () => {
      const result = client.setState('connecting');
      expect(result).toBe(true);
      
      const history = client.getStateHistory();
      expect(history).toHaveLength(1);
      expect(history[0].from).toBe('disconnected');
      expect(history[0].to).toBe('connecting');
      expect(history[0].timestamp).toBeInstanceOf(Date);
    });

    test('应该限制状态历史记录长度', () => {
      // 模拟超过100条记录
      for (let i = 0; i < 102; i++) {
        client.stateHistory.push({
          from: 'idle',
          to: 'audio_streaming',
          timestamp: new Date(),
          data: null
        });
      }
      
      client.setState('connecting');
      expect(client.getStateHistory()).toHaveLength(101); // 100 + 1新记录
    });
  });

  describe('状态变化回调', () => {
    test('应该正确注册和触发状态变化回调', () => {
      const callback = jest.fn();
      const callbackId = client.onStateChange(callback);
      
      expect(typeof callbackId).toBe('number');
      
      client.setState('connecting');
      
      expect(callback).toHaveBeenCalledWith('disconnected', 'connecting', null);
    });

    test('应该支持多个状态变化回调', () => {
      const callback1 = jest.fn();
      const callback2 = jest.fn();
      
      client.onStateChange(callback1);
      client.onStateChange(callback2);
      
      client.setState('connecting');
      
      expect(callback1).toHaveBeenCalledWith('disconnected', 'connecting', null);
      expect(callback2).toHaveBeenCalledWith('disconnected', 'connecting', null);
    });

    test('应该能够取消状态变化回调', () => {
      const callback = jest.fn();
      const callbackId = client.onStateChange(callback);
      
      const removed = client.offStateChange(callbackId);
      expect(removed).toBe(true);
      
      client.setState('connecting');
      expect(callback).not.toHaveBeenCalled();
    });

    test('状态变化回调异常不应影响状态转换', () => {
      const errorCallback = jest.fn(() => {
        throw new Error('Callback error');
      });
      const normalCallback = jest.fn();
      
      client.onStateChange(errorCallback);
      client.onStateChange(normalCallback);
      
      // 监听console.error
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      
      client.setState('connecting');
      
      expect(client.getCurrentState()).toBe('connecting');
      expect(normalCallback).toHaveBeenCalled();
      expect(consoleSpy).toHaveBeenCalledWith('State change callback error:', expect.any(Error));
      
      consoleSpy.mockRestore();
    });
  });

  describe('动作权限检查', () => {
    test('应该正确检查连接动作权限', () => {
      expect(client.canPerformAction('connect')).toBe(true);
      
      // 手动设置状态而不是通过setState来避免无效转换
      client.currentState = 'connecting';
      expect(client.canPerformAction('connect')).toBe(false);
    });

    test('应该正确检查模式选择权限', () => {
      expect(client.canPerformAction('selectMode')).toBe(false);
      
      // 手动设置状态序列
      client.currentState = 'idle';
      expect(client.canPerformAction('selectMode')).toBe(true);
    });

    test('应该正确检查音频发送权限', () => {
      expect(client.canPerformAction('sendAudio')).toBe(false);
      
      client.currentState = 'idle';
      expect(client.canPerformAction('sendAudio')).toBe(true);
      
      client.currentState = 'audio_streaming';
      expect(client.canPerformAction('sendAudio')).toBe(true);
    });

    test('应该正确检查结束音频权限', () => {
      expect(client.canPerformAction('endAudio')).toBe(false);
      
      client.currentState = 'audio_streaming';
      expect(client.canPerformAction('endAudio')).toBe(true);
      
      client.currentState = 'processing_audio';
      expect(client.canPerformAction('endAudio')).toBe(true);
    });

    test('应该正确检查文件上传权限', () => {
      expect(client.canPerformAction('uploadFile')).toBe(false);
      
      client.currentState = 'idle';
      expect(client.canPerformAction('uploadFile')).toBe(true);
    });
  });

  describe('WebSocket连接状态管理', () => {
    test('应该正确处理连接成功', () => {
      const openHandler = mockWs.on.mock.calls.find(call => call[0] === 'open')[1];
      
      client.setState('connecting');
      openHandler();
      
      expect(client.getCurrentState()).toBe('idle');
      expect(client.reconnectAttempts).toBe(0);
    });

    test('应该正确处理连接断开', () => {
      const closeHandler = mockWs.on.mock.calls.find(call => call[0] === 'close')[1];
      
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
      
      closeHandler();
      
      expect(client.getCurrentState()).toBe('reconnecting');
    });

    test('应该正确处理WebSocket错误', () => {
      const errorHandler = mockWs.on.mock.calls.find(call => call[0] === 'error')[1];
      const error = new Error('Connection failed');
      
      errorHandler(error);
      
      expect(client.getCurrentState()).toBe('error');
      expect(mockWs.close).toHaveBeenCalled();
    });

    test('应该在达到最大重连次数后停止重连', (done) => {
      client.reconnectAttempts = 5;
      client.maxReconnectAttempts = 5;
      
      const closeHandler = mockWs.on.mock.calls.find(call => call[0] === 'close')[1];
      closeHandler();
      
      setTimeout(() => {
        expect(client.getCurrentState()).toBe('error');
        done();
      }, 50);
    });
  });

  describe('服务器消息处理', () => {
    let messageHandler;

    beforeEach(() => {
      messageHandler = mockWs.on.mock.calls.find(call => call[0] === 'message')[1];
      // 设置为idle状态以便测试各种消息处理
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
    });

    test('应该正确处理模式选择确认消息', () => {
      const message = JSON.stringify({
        type: 'mode_selected',
        mode: 'realtime'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('idle');
    });

    test('应该正确处理识别结果消息', () => {
      client.setState('audio_streaming');
      client.setState('processing_audio');
      
      const message = JSON.stringify({
        type: 'recognition_result',
        text: 'Hello world'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('idle');
    });

    test('应该正确处理文件识别结果消息', () => {
      client.setState('file_uploading');
      client.setState('processing_file');
      
      const message = JSON.stringify({
        type: 'file_recognition_result',
        text: 'File content'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('idle');
    });

    test('应该正确处理音频处理开始消息', () => {
      client.setState('audio_streaming');
      
      const message = JSON.stringify({
        type: 'audio_processing_started'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('processing_audio');
    });

    test('应该正确处理文件上传完成消息', () => {
      client.setState('file_uploading');
      
      const message = JSON.stringify({
        type: 'file_upload_complete'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('processing_file');
    });

    test('应该正确处理服务器错误消息', () => {
      const message = JSON.stringify({
        type: 'error',
        message: 'Server error occurred'
      });
      
      messageHandler(message);
      expect(client.getCurrentState()).toBe('error');
    });

    test('应该处理无效JSON消息', () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      
      messageHandler('invalid json');
      
      expect(client.getCurrentState()).toBe('error');
      expect(consoleSpy).toHaveBeenCalledWith('Failed to parse message:', expect.any(Error));
      
      consoleSpy.mockRestore();
    });
  });

  describe('客户端操作方法', () => {
    beforeEach(() => {
      // 设置为可操作状态
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
    });

    test('selectMode应该在正确状态下工作', () => {
      const result = client.selectMode('realtime');
      
      expect(result).toBe(true);
      expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
        type: 'select_mode',
        mode: 'realtime'
      }));
    });

    test('selectMode应该在错误状态下失败', () => {
      client.setState('audio_streaming');
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
      
      const result = client.selectMode('realtime');
      
      expect(result).toBe(false);
      expect(mockWs.send).not.toHaveBeenCalled();
      expect(consoleSpy).toHaveBeenCalledWith('Cannot select mode in current state: audio_streaming');
      
      consoleSpy.mockRestore();
    });

    test('startAudioStream应该正确设置状态', () => {
      const result = client.startAudioStream();
      
      expect(result).toBe(true);
      expect(client.getCurrentState()).toBe('audio_streaming');
    });

    test('sendAudioChunk应该发送音频数据', () => {
      const audioData = Buffer.from('test audio data');
      const result = client.sendAudioChunk(audioData);
      
      expect(result).toBe(true);
      expect(client.getCurrentState()).toBe('audio_streaming');
      expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
        type: 'audio_chunk',
        audio: audioData.toString('base64')
      }));
    });

    test('endAudio应该结束音频流', () => {
      client.setState('audio_streaming');
      const result = client.endAudio();
      
      expect(result).toBe(true);
      expect(client.getCurrentState()).toBe('processing_audio');
      expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
        type: 'end_audio'
      }));
    });

    test('uploadFile应该上传文件', () => {
      const fileData = Buffer.from('test file data');
      const filename = 'test.wav';
      const result = client.uploadFile(fileData, filename);
      
      expect(result).toBe(true);
      expect(client.getCurrentState()).toBe('file_uploading');
      expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
        type: 'file_upload',
        file_data: fileData.toString('base64'),
        filename: filename
      }));
    });

    test('WebSocket发送失败应该设置错误状态', () => {
      mockWs.send.mockImplementation(() => {
        throw new Error('Send failed');
      });
      
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      const result = client.selectMode('realtime');
      
      expect(result).toBe(false);
      expect(client.getCurrentState()).toBe('error');
      expect(consoleSpy).toHaveBeenCalledWith('Failed to send mode selection:', expect.any(Error));
      
      consoleSpy.mockRestore();
    });
  });

  describe('状态查询方法', () => {
    test('isConnected应该正确判断连接状态', () => {
      expect(client.isConnected()).toBe(false);
      
      client.setState('connecting');
      client.setState('connected');
      expect(client.isConnected()).toBe(true);
      
      client.setState('idle');
      expect(client.isConnected()).toBe(true);
      
      client.setState('audio_streaming');
      expect(client.isConnected()).toBe(true);
      
      client.setState('disconnected');
      expect(client.isConnected()).toBe(false);
    });

    test('isIdle应该正确判断空闲状态', () => {
      expect(client.isIdle()).toBe(false);
      
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
      expect(client.isIdle()).toBe(true);
      
      client.setState('audio_streaming');
      expect(client.isIdle()).toBe(false);
    });

    test('isProcessing应该正确判断处理状态', () => {
      expect(client.isProcessing()).toBe(false);
      
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
      expect(client.isProcessing()).toBe(false);
      
      client.setState('audio_streaming');
      expect(client.isProcessing()).toBe(true);
      
      client.setState('processing_audio');
      expect(client.isProcessing()).toBe(true);
      
      client.setState('file_uploading');
      expect(client.isProcessing()).toBe(true);
      
      client.setState('processing_file');
      expect(client.isProcessing()).toBe(true);
    });

    test('hasError应该正确判断错误状态', () => {
      expect(client.hasError()).toBe(false);
      
      client.setState('error');
      expect(client.hasError()).toBe(true);
    });

    test('resetError应该重置错误状态', () => {
      client.setState('error');
      expect(client.hasError()).toBe(true);
      
      const result = client.resetError();
      expect(result).toBe(true);
      expect(client.getCurrentState()).toBe('disconnected');
      expect(client.hasError()).toBe(false);
    });

    test('resetError在非错误状态下应该返回false', () => {
      client.setState('connecting');
      client.setState('connected');
      client.setState('idle');
      
      const result = client.resetError();
      expect(result).toBe(false);
      expect(client.getCurrentState()).toBe('idle');
    });
  });

  describe('边界情况和错误处理', () => {
    test('应该处理状态转换中的数据传递', () => {
      const testData = { result: 'test result' };
      client.setState('connecting', testData);
      
      const history = client.getStateHistory();
      expect(history[0].data).toEqual(testData);
    });

    test('应该正确处理未知消息类型', () => {
      const messageHandler = mockWs.on.mock.calls.find(call => call[0] === 'message')[1];
      const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
      
      const message = JSON.stringify({
        type: 'unknown_type',
        data: 'test'
      });
      
      messageHandler(message);
      
      expect(consoleSpy).toHaveBeenCalledWith('Unknown message type:', 'unknown_type');
      consoleSpy.mockRestore();
    });

    test('应该处理重连逻辑', (done) => {
      client.reconnectAttempts = 2;
      client.maxReconnectAttempts = 5;
      
      // Mock setTimeout
      const originalSetTimeout = global.setTimeout;
      global.setTimeout = jest.fn((callback, delay) => {
        expect(delay).toBe(1000);
        callback();
      });
      
      const closeHandler = mockWs.on.mock.calls.find(call => call[0] === 'close')[1];
      closeHandler();
      
      setTimeout(() => {
        expect(client.getCurrentState()).toBe('connecting');
        expect(client.reconnectAttempts).toBe(3);
        global.setTimeout = originalSetTimeout;
        done();
      }, 50);
    });

    test('应该在无效状态转换时保持当前状态', () => {
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
      const originalState = client.getCurrentState();
      
      const result = client.setState('invalid_state');
      
      expect(result).toBe(false);
      expect(client.getCurrentState()).toBe(originalState);
      expect(consoleSpy).toHaveBeenCalledWith('Invalid state transition from disconnected to invalid_state');
      
      consoleSpy.mockRestore();
    });
  });

  describe('完整工作流程测试', () => {
    test('应该正确执行完整的音频识别流程', () => {
      const stateChanges = [];
      client.onStateChange((from, to) => {
        stateChanges.push({ from, to });
      });
      
      // 模拟连接过程
      client.setState('connecting');
      const openHandler = mockWs.on.mock.calls.find(call => call[0] === 'open')[1];
      openHandler();
      
      // 选择模式
      client.selectMode('realtime');
      
      // 开始音频流
      client.startAudioStream();
      
      // 发送音频数据
      const audioData = Buffer.from('audio');
      client.sendAudioChunk(audioData);
      
      // 结束音频
      client.endAudio();
      
      // 模拟服务器响应
      const messageHandler = mockWs.on.mock.calls.find(call => call[0] === 'message')[1];
      messageHandler(JSON.stringify({
        type: 'recognition_result',
        text: 'Hello world'
      }));
      
      // 验证状态变化序列
      const expectedStates = [
        { from: 'disconnected', to: 'connecting' },
        { from: 'connecting', to: 'connected' },
        { from: 'connected', to: 'idle' },
        { from: 'idle', to: 'audio_streaming' },
        { from: 'audio_streaming', to: 'processing_audio' },
        { from: 'processing_audio', to: 'idle' }
      ];
      
      expect(stateChanges).toEqual(expectedStates);
      expect(client.getCurrentState()).toBe('idle');
    });

    test('应该正确执行完整的文件上传流程', () => {
      const stateChanges = [];
      client.onStateChange((from, to) => {
        stateChanges.push({ from, to });
      });
      
      // 模拟连接过程
      client.setState('connecting');
      const openHandler = mockWs.on.mock.calls.find(call => call[0] === 'open')[1];
      openHandler();
      
      // 上传文件
      const fileData = Buffer.from('file content');
      client.uploadFile(fileData, 'test.wav');
      
      // 模拟服务器响应
      const messageHandler = mockWs.on.mock.calls.find(call => call[0] === 'message')[1];
      messageHandler(JSON.stringify({
        type: 'file_upload_complete'
      }));
      
      messageHandler(JSON.stringify({
        type: 'file_recognition_result',
        text: 'File result'
      }));
      
      // 验证状态变化序列
      const expectedStates = [
        { from: 'disconnected', to: 'connecting' },
        { from: 'connecting', to: 'connected' },
        { from: 'connected', to: 'idle' },
        { from: 'idle', to: 'file_uploading' },
        { from: 'file_uploading', to: 'processing_file' },
        { from: 'processing_file', to: 'idle' }
      ];
      
      expect(stateChanges).toEqual(expectedStates);
      expect(client.getCurrentState()).toBe('idle');
    });
  });
});